//
// Copyright (C) 2006 Andras Varga
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
//

#include <algorithm>
#include <math.h>
#include "IRoutingTable.h"
#include "IInterfaceTable.h"
#include "IPAddressResolver.h"
#include "NetworkConfigurator.h"
#include "IPv4InterfaceData.h"

#define MINWINSIZE 5 // minimum window size: to distribute the load
#define MINVLANSIZE 20 // minimum number of VLAn size


Define_Module(NetworkConfigurator);


void NetworkConfigurator::initialize(int stage)
{
    if (stage==2)
    {
    	gap = par("gap");
    	if (gap < 0)
    		gap = 0;

        cTopology topo("topo");
        NodeInfoVector nodeInfo; // will be of size topo.nodes[]

        // extract topology into the cTopology object, then fill in
        // isIPNode, rt and ift members of nodeInfo[]
        extractTopology(topo, nodeInfo);

        // assign addresses to IP nodes, and also store result in nodeInfo[].address
        assignAddresses(topo, nodeInfo);

        // add routes for point-to-point peers
        addPointToPointPeerRoutes(topo, nodeInfo);

        // set the mapping <end-host, edge-switch>
        setEdgeSwitchEntries(topo, nodeInfo);

        // set Hash Routing Table
        fillHashRoutingTable(topo, nodeInfo);

        // add default routes to hosts (nodes with a single attachment);
        // also remember result in nodeInfo[].usesDefaultRoute
//        addDefaultRoutes(topo, nodeInfo);


        // update display string
        setDisplayString(topo, nodeInfo);

        // dumping the information
//        dump(topo, nodeInfo);
    }

//    // Nam Dang's network configurator
//	{
//		cTopology topo("topo");
//		NodeInfoVector nodeInfo; // will be of size topo.nodes[]
//
//		// extract topology into the cTopology object, then fill in
//		// isIPNode, rt and ift members of nodeInfo[]
//		extractTopology(topo, nodeInfo);
//
//		// assign addresses to IP nodes, and also store result in nodeInfo[].address
//		assignAddresses(topo, nodeInfo);
//	}
}

void NetworkConfigurator::extractTopology(cTopology& topo, NodeInfoVector& nodeInfo)
{
    // extract topology
    topo.extractByProperty("node");
    EV << "cTopology found " << topo.getNumNodes() << " nodes\n";

    // fill in isIPNode, ift and rt members in nodeInfo[]
    nodeInfo.resize(topo.getNumNodes());
    for (int i=0; i<topo.getNumNodes(); i++)
    {
        cModule *mod = topo.getNode(i)->getModule();
        nodeInfo[i].ift = IPAddressResolver().findInterfaceTableOf(mod);
        nodeInfo[i].rt = IPAddressResolver().findRoutingTableOf(mod);
        nodeInfo[i].isIPNode = nodeInfo[i].rt!=NULL;
        nodeInfo[i].isEdge = false;

        // count non-loopback interfaces
        int numIntf = 0;
        IInterfaceTable *ift = nodeInfo[i].ift;
        for (int j = 0; j < ift->getNumInterfaces(); j++) {
        	if (!ift->getInterface(j)->isLoopback())
        		numIntf++;
        }

        if (numIntf != 1) {
        	nodeInfo[i].isRouter = true;
        	nodeInfo[i].rt->setRouter();
        }
        else
        	nodeInfo[i].isRouter = false;
    }

    for (int i = 0; i < topo.getNumNodes(); i++) {
    	// skip non-ip node and non-router node
    	if (!nodeInfo[i].isIPNode || !nodeInfo[i].isRouter)
    		continue;

    	cTopology::Node *node = topo.getNode(i);
    	int outLinks = node->getNumOutLinks();
    	for (int j = 0; j < outLinks; j++) {
			cTopology::Node *neighbor = node->getLinkOut(j)->getRemoteNode();

			// find neighbour's index in cTopology ==> k
			int k;
			for (k=0; k<topo.getNumNodes(); k++)
				if (topo.getNode(k)== neighbor)
					break;
			ASSERT(k < topo.getNumNodes()); // k should be in range?

			if (!nodeInfo[k].isRouter) {
				nodeInfo[i].isEdge = true;
				nodeInfo[i].rt->setEdge();
				EV << "Node " << i << " is an edge router" << endl;
				break;
			}
		}
	}
}

void NetworkConfigurator::assignAddresses(cTopology& topo, NodeInfoVector& nodeInfo)
{
    uint32 base = 10 << 24;  // 10.x.x.x addresses
    int nodeCtr = 1;         // middle 16 bits

    for (int i=0; i<topo.getNumNodes(); i++)
    {
        // skip bus types
        if (!nodeInfo[i].isIPNode)
            continue;

        uint32 addr = base + (nodeCtr++ << 8);   // --> 10.nn.nn.0

        // assign address to all (non-loopback) interfaces
        IInterfaceTable *ift = nodeInfo[i].ift;
        for (int k=0; k<ift->getNumInterfaces(); k++)
        {
            InterfaceEntry *ie = ift->getInterface(k);
            if (!ie->isLoopback())
            {
                ie->ipv4Data()->setIPAddress(IPAddress(addr | (uint32)k));
                ie->ipv4Data()->setNetmask(IPAddress::ALLONES_ADDRESS); // full address must match for local delivery
            }
        }

        // set routerId as well (if not yet configured)
        IRoutingTable *rt = nodeInfo[i].rt;
        if (rt->getRouterId().isUnspecified())
        {
            rt->setRouterId(IPAddress(addr | 1U)); // 10.nn.nn.1
        }
    }
}

void NetworkConfigurator::addPointToPointPeerRoutes(cTopology& topo, NodeInfoVector& nodeInfo)
{
    bool useRouterIdForRoutes = true; // TODO make it parameter
    size_t vlanCnt = 0; // the number of VLANs = max(no of neighbors)

    // add routes towards point-to-point routers (in real life these routes are
    // created automatically after PPP handshake when neighbour's address is learned)
    for (int i=0; i<topo.getNumNodes(); i++)
    {
        // skip bus types
        if (!nodeInfo[i].isIPNode)
            continue;

        cTopology::Node *node = topo.getNode(i);
        //InterfaceTable *ift = nodeInfo[i].ift;
        IRoutingTable *rt = nodeInfo[i].rt;

        // loop through neighbors
        for (int j=0; j<node->getNumOutLinks(); j++)
        {
            cTopology::Node *neighbor = node->getLinkOut(j)->getRemoteNode();

            // find neighbour's index in cTopology ==> k
            int k;
            for (k=0; k<topo.getNumNodes(); k++)
                if (topo.getNode(k)==neighbor)
                    break;
            ASSERT(k<=topo.getNumNodes());

            // if it's not an IP getNode(e.g. an Ethernet switch), then we're not interested
            if (!nodeInfo[k].isIPNode)
                continue;

            // find out neighbor's routerId
            IPAddress neighborRouterId = nodeInfo[k].rt->getRouterId();

            // Add neighbor to the current node
            rt->addNeighbor(neighborRouterId);

            // find out neighbor's interface IP address
            int neighborGateId = node->getLinkOut(j)->getRemoteGate()->getId();
            InterfaceEntry *neighborIe = nodeInfo[k].ift->getInterfaceByNodeInputGateId(neighborGateId);
            ASSERT(neighborIe);
            IPAddress neighborAddr = neighborIe->ipv4Data()->getIPAddress();

            // find our own interface towards neighbor
            int gateId = node->getLinkOut(j)->getLocalGate()->getId();
            InterfaceEntry *ie = nodeInfo[i].ift->getInterfaceByNodeOutputGateId(gateId);
            ASSERT(ie);

            // add route
            EV << " Added route from " << rt->getRouterId() << " to " << neighborAddr << endl;
            IPRoute *e = new IPRoute();
            if (useRouterIdForRoutes)
            {
                e->setHost(neighborRouterId);
                e->setGateway(neighborAddr);
            }
            else
            {
                e->setHost(neighborAddr); // and no gateway
            }
            e->setNetmask(IPAddress(255,255,255,255)); // full match needed
            e->setInterface(ie);
            e->setType(IPRoute::DIRECT);
            e->setSource(IPRoute::MANUAL);
            //e->getMetric() = 1;
            rt->addRoute(e);
        }

        // Store the largest count
        size_t nbCnt = rt->getNeighborCnt();
        if (vlanCnt < nbCnt)
        	vlanCnt = nbCnt;

        EV << " Node with IP = " << rt->getRouterId() << " has " << nbCnt << " neighbors " << endl;
    }
}

void NetworkConfigurator::addDefaultRoutes(cTopology& topo, NodeInfoVector& nodeInfo)
{
    // add default route to nodes with exactly one (non-loopback) interface
    for (int i=0; i<topo.getNumNodes(); i++)
    {
        // skip bus types
        if (!nodeInfo[i].isIPNode)
            continue;

        cTopology::Node *node = topo.getNode(i);
        IInterfaceTable *ift = nodeInfo[i].ift;
        IRoutingTable *rt = nodeInfo[i].rt;

        // count non-loopback interfaces
        int numIntf = 0;
        InterfaceEntry *ie = NULL;
        for (int k=0; k<ift->getNumInterfaces(); k++)
            if (!ift->getInterface(k)->isLoopback())
                {ie = ift->getInterface(k); numIntf++;}

        nodeInfo[i].usesDefaultRoute = (numIntf==1);
        if (numIntf!=1)
            continue; // only deal with nodes with one interface plus loopback

        EV << "  " << node->getModule()->getFullName() << " has only one (non-loopback) "
           "interface, adding default route\n";

        // add route
        IPRoute *e = new IPRoute();
        e->setHost(IPAddress());
        e->setNetmask(IPAddress());
        e->setInterface(ie);
        e->setType(IPRoute::REMOTE);
        e->setSource(IPRoute::MANUAL);
        //e->setMetric(1);
        rt->addRoute(e);
    }
}

void NetworkConfigurator::setPeersParameter(const char *submodName, cTopology& topo, NodeInfoVector& nodeInfo)
{
    // the RSVP module expects a "peers" module parameter to contain the interfaces
    // towards directly connected other RSVP routers. Since it's cumbersome to configure
    // manually in a large network, do it here (submodName = "rsvp").
    // The LinkStateRouting module is similar, so this function is also called with submodName = "LinkStateRouting".

    // for each RSVP router, collect neighbors which are also RSVP routers
    for (int i=0; i<topo.getNumNodes(); i++)
    {
        // if it doesn't have an RSVP submodule, we're not interested
        cModule *submod = topo.getNode(i)->getModule()->getSubmodule(submodName);
        if (submod==NULL)
            continue;

        std::string peers;
        cTopology::Node *node = topo.getNode(i);
        for (int j=0; j<node->getNumOutLinks(); j++)
        {
            // if neighbor is not an RSVP router, then we're not interested
            cModule *neighborSubmod = node->getLinkOut(j)->getRemoteNode()->getModule()->getSubmodule(submodName);
            if (neighborSubmod==NULL)
                continue;

            // find our own interface towards neighbor
            int gateId = node->getLinkOut(j)->getLocalGate()->getId();
            InterfaceEntry *ie = nodeInfo[i].ift->getInterfaceByNodeOutputGateId(gateId);
            ASSERT(ie);

            // interface name to peers list
            peers += std::string(" ") + ie->getName();
        }

        // set "peers" parameter
        submod->par("peers") = peers.c_str();
    }
}

void NetworkConfigurator::fillRoutingTables(cTopology& topo, NodeInfoVector& nodeInfo)
{
/* FIXME TBD
    // fill in routing tables with static routes
    for (int i=0; i<topo.getNumNodes(); i++)
    {
        cTopology::Node *destNode = topo.getNode(i);

        // skip bus types
        if (!nodeInfo[i].isIPNode)
            continue;

        IPAddress destAddr = nodeInfo[i].address;
        std::string destModName = destNode->getModule()->getFullName();

        // calculate shortest paths from everywhere towards destNode
        topo.calculateUnweightedSingleShortestPathsTo(destNode);

        // add route (with host=destNode) to every routing table in the network
        // (excepting nodes with only one interface -- there we'll set up a default route)
        for (int j=0; j<topo.getNumNodes(); j++)
        {
            if (i==j) continue;
            if (!nodeInfo[j].isIPNode)
                continue;

            cTopology::Node *atNode = topo.getNode(j);
            if (atNode->getNumPaths()==0)
                continue; // not connected
            if (nodeInfo[j].usesDefaultRoute)
                continue; // already added default route here

            IPAddress atAddr = nodeInfo[j].address;

            IInterfaceTable *ift = nodeInfo[j].ift;

            int outputGateId = atNode->getPath(0)->getLocalGate()->getId();
            InterfaceEntry *ie = ift->getInterfaceByNodeOutputGateId(outputGateId);
            if (!ie)
                error("%s has no interface for output gate id %d", ift->getFullPath().c_str(), outputGateId);

            EV << "  from " << atNode->getModule()->getFullName() << "=" << IPAddress(atAddr);
            EV << " towards " << destModName << "=" << IPAddress(destAddr) << " interface " << ie->getName() << endl;

            // add route
            IRoutingTable *rt = nodeInfo[j].rt;
            IPRoute *e = new IPRoute();
            e->setHost(destAddr);
            e->gateway = ???
            e->setNetmask(IPAddress(255,255,255,255)); // full match needed
            e->setInterface(ie);
            e->setType(IPRoute::REMOTE);
            e->setSource(IPRoute::MANUAL);
            //e->getMetric() = 1;
            rt->addRoute(e);
        }
    }
*/
}

void NetworkConfigurator::handleMessage(cMessage *msg)
{
    error("this module doesn't handle messages, it runs only in initialize()");
}

void NetworkConfigurator::setDisplayString(cTopology& topo, NodeInfoVector& nodeInfo)
{
    int numIPNodes = 0;
    for (int i=0; i<topo.getNumNodes(); i++)
        if (nodeInfo[i].isIPNode)
            numIPNodes++;

    // update display string
    char buf[80];
    sprintf(buf, "%d IP nodes\n%d non-IP nodes", numIPNodes, topo.getNumNodes()-numIPNodes);
    getDisplayString().setTagArg("t",0,buf);
}

void NetworkConfigurator::setEdgeSwitchEntries(cTopology& topo,	NodeInfoVector& nodeInfo) {
	std::map<IPAddress, IPAddress> eSwitchMap;

	for (int i = 0; i < topo.getNumNodes(); i++) {
		// skip bus types or router types
		if (!nodeInfo[i].isIPNode || nodeInfo[i].isRouter)
			continue;

		IRoutingTable *rt = nodeInfo[i].rt;
		ASSERT(rt->getNeighborCnt() == 1); // should have only 1 neighbor

		IPAddress eSwitchIP = rt->getNeighbor(0);
		ASSERT(eSwitchIP != IPAddress::UNSPECIFIED_ADDRESS);

		eSwitchMap.insert(std::pair<IPAddress, IPAddress> (rt->getRouterId(), eSwitchIP));

		EV << "Dest host: " << rt->getRouterId() << " Edge switch: " << eSwitchIP << endl;
	}

	EV << "There are " << eSwitchMap.size() << " entries in the edge-switch map " << endl;

	// update the edge switch with new map
	for (int i = 0; i < topo.getNumNodes(); i++) {
		if (!nodeInfo[i].isIPNode)
			continue;

		std::map<IPAddress, IPAddress>::iterator it;
		for (it = eSwitchMap.begin(); it != eSwitchMap.end(); it++ ) {
			nodeInfo[i].rt->addEdgeSwitchEntry(it->first, it->second);
		}
	}
}

void NetworkConfigurator::fillHashRoutingTable(cTopology& topo, NodeInfoVector& nodeInfo) {
	for (int i = 0; i < topo.getNumNodes(); i++) {
		// calculate distances to ege-routers only
		if (!nodeInfo[i].isEdge)
			continue;

		// this is an edge-router
		// we will find the shortest routes from each router to this edge-router
		cTopology::Node *dest = topo.getNode(i);
		topo.calculateUnweightedSingleShortestPathsTo(dest);
		IPAddress destAddr = nodeInfo[i].rt->getRouterId();

		// write the shortest path to DEST to every node
		for (int j = 0; j < topo.getNumNodes(); j++) {
			if (!nodeInfo[j].isIPNode || j == i)
				continue;

			cTopology::Node *node = topo.getNode(j);
			nodeInfo[j].shortestPathLen = node->getDistanceToTarget();

			// Determine nextHop entry in the shortest path
			cTopology::Node *nextHop = node->getPath(0)->getRemoteNode();
			// find neighbour's index in cTopology ==> k
			int k;
			for (k = 0; k < topo.getNumNodes(); k++)
				if (topo.getNode(k) == nextHop)
					break;
			ASSERT(k<=topo.getNumNodes());
			IPAddress nextHopAddr = nodeInfo[k].rt->getRouterId();

			nodeInfo[j].rt->addNextHop(destAddr, nextHopAddr);
		}

		// disable each router one-by-one to find alternate paths (edge router maybe connected
		// directly to each other, so we should disable them to find alternate paths as well.
		for (int j = 0; j < topo.getNumNodes(); j++) {
			// node should be a router (it's meaningless to disable an end-host)
			if (!nodeInfo[j].isIPNode || !nodeInfo[j].isRouter || j == i)
				continue;

			cTopology::Node *node = topo.getNode(j);
			node->disable();

			// Re-calculate the shortest-path
			topo.calculateUnweightedSingleShortestPathsTo(dest);

			// Update the table
			for (int l = 0; l < topo.getNumNodes(); l++) {
				// only calculate the distance from router
				if (!nodeInfo[l].isIPNode || !nodeInfo[l].isRouter || l == j || l == i)
					continue;

				// skip if the length is too long
				cTopology::Node *alterNode = topo.getNode(l);
				double dist = alterNode->getDistanceToTarget();
				if (dist == INFINITY || dist > nodeInfo[l].shortestPathLen + gap) // if there is a path
					continue;

				cTopology::Node *nextHop = alterNode->getPath(0)->getRemoteNode();
				// find neighbour's index in cTopology ==> k
				int k;
				for (k = 0; k < topo.getNumNodes(); k++)
					if (topo.getNode(k) == nextHop)
						break;
				ASSERT(k<=topo.getNumNodes());
				IPAddress nextHopAddr = nodeInfo[k].rt->getRouterId();

				nodeInfo[l].rt->addNextHop(destAddr, nextHopAddr);
			}

			node->enable();
		}
	}

	// Now calculate the bit-window size
	size_t maxSize = 0;
	for (int i = 0; i < topo.getNumNodes(); i++) {
		// skip if this is an end-host
		if (!nodeInfo[i].isRouter)
			continue;

		IRoutingTable *rt = nodeInfo[i].rt;

		// pick a destination edge router and get the nextHop list size
		// corresponding to this edge router
		for (int j = 0; j < topo.getNumNodes(); j++) {
			if (!nodeInfo[j].isEdge || i == j)
				continue;

			// get the size of the nextHop list from i to j
			size_t nextHopListSize = rt->getNextHopListSize(nodeInfo[j].rt->getRouterId());
			ASSERT(nextHopListSize > 0); // should be > 0, or something is wrong

			if (maxSize < nextHopListSize)
				maxSize = nextHopListSize;
		}
	}

	size_t winSize = (size_t) ceil(log(maxSize)/log(2));
	EV << "Actual win size: " << winSize << " Actual VLAN size is: " << maxSize << endl;
	if (winSize < MINWINSIZE) {
		winSize = MINWINSIZE;
		maxSize = MINVLANSIZE;
	}


	EV << "Window size is: " << winSize << " VLAN size is: " << maxSize << endl;
	for (int i = 0; i < topo.getNumNodes(); i++) {
		if (!nodeInfo[i].isRouter)
			continue;

		nodeInfo[i].rt->setWinSize(winSize);
		nodeInfo[i].rt->setVLANSize(maxSize);

	}
}

void NetworkConfigurator::dump(cTopology& topo, NodeInfoVector& nodeInfo) {
	for (int i = 0; i < topo.getNumNodes(); i++) {
		if (!nodeInfo[i].isIPNode)
			continue;

		nodeInfo[i].rt->dump();
	}
}
